/***********************************
 * Jonathan Woolery & Ricardo Tijero
 * CPE453 - Winter 2013
 * F.Khosmood
 * Project #4
 * *********************************/
#include "libTinyFS.h"
#include "libDisk.h"

int disk = 0;
char *null = '\0';
FileObj *filesOnDisk = NULL;
FileObj *openFiles = NULL;
/*filesOnDisk = newFileObj(null, 0, 0, 0);
  openFiles = newFileObj(null, 0, 0, 0);
 */
char blockStateArr[41] = {0,0,0,0,0,0,0,0,0,0,
                          0,0,0,0,0,0,0,0,0,0,
                          0,0,0,0,0,0,0,0,0,0,
                          0,0,0,0,0,0,0,0,0,0,0};

fileDescriptor tfs_openFile(char *name)
{
    int block, i;
    char *pos, buf[256];
    FileObj *tmp;
    FileObj *trav;

    if(disk <= 0)
    {
        if((disk = openDisk(defaultDiskName, 0)) == -1)
        {
            printf("failed to open %s\n", defaultDiskName);
            return -1;
        }
    }

    if(filesOnDisk == NULL)
    {
        filesOnDisk = newFileObj(NULL, 0, 0, RW);
        populateFileList(disk);
    }

    if(openFiles == NULL)
        openFiles = newFileObj(NULL, 0, 0, RW);
    
    trav = filesOnDisk->next;
    
    if( blockStateArr[0] == '\0')
            populateBlockStateArr(disk);

    while(trav != NULL)
    {
        if(strcmp(name, trav->filename) == 0)
        {
            tmp = newFileObj(trav->filename, trav->block, trav->size, trav->mode);
            tmp->fp = 0;
            tmp->next = openFiles->next;
            openFiles->next = tmp;

            return tmp->block;
        }
        else
        {
            trav = trav->next;
        }
    }

    /* find first free block */
    if ((block = nextFreeBlk()) == 0)
    {
        printf("Line %d: Not enough memory to create new file.\n",__LINE__);
        return -1;
    }

    /* change state of block*/
    blockStateArr[block] = '1';

    for(i = 0; i < blockSize; i++)
        buf[i] = 0;
    /* make block into inode */
    buf[0] = INODE;
    buf[1] = MAGIC_NUM;
    buf[2] = 0;
    buf[3] = 0;
    pos = &(buf[4]);
    strcpy(pos, name);
    if(writeBlock(disk, block, buf) == -1)
    {
        printf("write block failed\n");
        return -1;
    }
    /*add file to filesOnDisk and openFiles */
    tmp = newFileObj(name, block, 0, RW);
    tmp->fp = 0;
    tmp->next = openFiles->next;
    openFiles->next = tmp;

    tmp = newFileObj(name, block, 0, RW);
    tmp->fp = 0;
    tmp->next = filesOnDisk->next;
    filesOnDisk->next = tmp;


    /* update free list (bit vector) in superblock */
    writeVector();

    return block; /* stand in for File Descriptor == inode block for file*/
}

int tfs_closeFile(fileDescriptor FD)
{
    FileObj *prev = openFiles;
    FileObj *trav = openFiles->next;

    while(trav != NULL)
    {
        if(trav->block == FD)
        {
            prev->next = trav->next;
            free(trav);
            return 0;
        }

        prev = prev->next;
        trav = trav->next;
    }
    return -1;
}

int tfs_removeFileFromList(fileDescriptor FD)
{
    FileObj *prev = filesOnDisk;
    FileObj *trav = filesOnDisk->next;

    while(trav != NULL)
    {
        if(trav->block == FD)
        {
            prev->next = trav->next;
            free(trav);
            return 0;
        }

        prev = prev->next;
        trav = trav->next;
    }
    return -1;
}

int tfs_writeFile(fileDescriptor FD, char *buffer)
{
    /* calculate bytes needed for buffer */
    /* determine number of blocks needed */
    /* start with 239 available in iNode */
    /* find first free block (or set of free blocks) and link from iNode */
    /* continue with 252 bytes avail in Extents */
    char buf[blockSize + 1];
    unsigned char fileSize[5];
    char *srcPos, *destPos;
    int curBlock, nextBlock, neededBlks, bufSize, i;
    FileObj *fileInfo;
    
    if((fileInfo = getFileEntry(openFiles, FD)) == NULL)
    {
        printf("Line %d: file not found\n", __LINE__);
        return -1;
    }
    if(fileInfo->mode == RO)
    {
        printf("WriteFile failed: %s is read only.\n", fileInfo->filename);
        return -1;
    }

	nextBlock = 0;
    srcPos = buffer;
    destPos = buf;
    bufSize = strlen(buffer) + INODE_META;
    neededBlks = bufSize / DATA_SIZE;
    neededBlks = (bufSize % DATA_SIZE) ? neededBlks + 1: neededBlks;

    if (totalFreeBlks() < neededBlks)
    {
        printf("Line %d: Not enough memory to write file.\n", __LINE__);
        return -1;
    }

    
    freeChain(FD);
    
    blockStateArr[FD] = '1';
    if(neededBlks > 1)
        nextBlock = nextFreeBlk();
    
    splitInt((unsigned) strlen(buffer), fileSize);
    *(destPos++) = INODE;
    *(destPos++) = MAGIC_NUM;
    *(destPos++) = nextBlock;
    *(destPos++) = 0;
    strncpy(destPos, fileInfo->filename, FILE_NAME_SIZE);
    destPos += FILE_NAME_SIZE;
    strcpy(destPos, (char*)fileSize);
    destPos += sizeof(int);
    strncpy(destPos, buffer, DATA_SIZE - INODE_META);
    if(writeBlock(disk, FD, buf) == -1)
    {
        printf("write block failed\n");
        return -1;
    }

    if (neededBlks > 1)
        srcPos += DATA_SIZE - INODE_META;

    for (i = 1; i < neededBlks; i++)
    {
        destPos = buf;
        curBlock = nextBlock;
        blockStateArr[curBlock] = '1';
        if (i + 1 == neededBlks)
            nextBlock = 0;
        else
            nextBlock = nextFreeBlk();
        *(destPos++) = FILE_EXTENT;
        *(++destPos) = nextBlock;
        destPos += 2;
        strncpy(destPos, srcPos, DATA_SIZE);
        if(  writeBlock(disk, curBlock, buf) == -1)
        {
            printf("write block failed\n");
            return -1;
        }
        srcPos += DATA_SIZE;
    }

    fileInfo->size = (unsigned) (bufSize - INODE_META);
    writeVector();
    fileInfo->fp = 0;
    
    return 0;
}

int tfs_deleteFile(fileDescriptor FD)
{
    char curBlock[blockSize];
    int nextB;
    char newBlockType = FREE;
    int currB = FD;
    FileObj *target;

    if( (target = getFileEntry(filesOnDisk, FD)) == NULL)
    {
        printf("No such file on disk.");
        return -1;
    }

    if(target->mode == RO)
    {
        printf("delete failed: %s is read only\n", target->filename);
        return -1;
    }

    readBlock(disk, currB, curBlock);
    nextB = curBlock[2];


    while( currB != 0)
    {
        if( writeBlock(disk, currB, &newBlockType)== -1)
        {
            printf("write block failed\n");
            return -1;
        }

        blockStateArr[currB] = '0';

        if(readBlock(disk, nextB, curBlock) == -1)
        {
            printf("ReadBlock error\n");
            return -1;
        }

        currB = nextB;
        nextB = curBlock[2];
    }

    writeVector();
    tfs_closeFile(FD);
    tfs_removeFileFromList(FD);
    return 0;
}

int tfs_readByte(fileDescriptor FD, char *buffer)
{
    FileObj *aFile;
    char curBlock[blockSize];
    int numBlocks;
    int nBytes;
    int i;
    int currB = FD;

    if((aFile = getFileEntry(openFiles, FD)) == NULL)
    {
        printf("Unable to find file.\n");
        return -1;
    }

    if(aFile->fp >= aFile->size)
    {
        /* printf("Error L%d: readByte at end of file\n", __LINE__);*/
        return -1;
    }

    numBlocks = ((aFile->fp + INODE_META) / DATA_SIZE) + 1;
    nBytes = ((aFile->fp + INODE_META) % DATA_SIZE) + BLOCK_META;
    
    for (i = 0; i < numBlocks; i++)
    {
        if (readBlock(disk, currB, curBlock) == -1)
        {
            printf("ReadBlock error\n");
            return -1;
        }
        currB = curBlock[2];
    }

    buffer[0] = curBlock[nBytes];
    aFile->fp += 1;
    return 0;
}

int tfs_seek(fileDescriptor FD, int offset)
{
    FileObj *aFile;

    if (offset < 0)
    {
        printf("Offset can't be negative.\n");
        return -1;
    }
    if ((aFile = getFileEntry(openFiles, FD)) == NULL)
    {
        printf("Unable to find file.\n");
        return -1;
    }
    if (aFile->size < offset)
    {
        printf("offset too large.\n");
        return -1;
    }

    aFile->fp = offset;
    return 0;
}

/***************************************
 * Additional functionality functions  *
 *************************************************************/


/**
 * Rename a file on TFS. Returns 0 on success, -1 on failure.
 */
int tfs_rename(char *oldName, char *newName)
{
    char buf[256];
    FileObj *trav = filesOnDisk->next;
    FileObj *openTrav = openFiles->next;

    if(disk <= 0)
    {
        if((disk = openDisk(defaultDiskName, 0) == -1))
        {
            printf("failed to open %s\n", defaultDiskName);
            return -1;
        }
    }
    
    while(trav != NULL)
    {
        if(strcmp(newName, trav->filename) == 0)
        {
            printf("conflict: file with that name already exists\n");
            return -1;
        }
        else
        {
            trav = trav->next;
        }
    }

    trav = filesOnDisk->next;

    while(trav != NULL)
    {
        if(strcmp(oldName, trav->filename) == 0)
        {
            if(trav->mode == RO)
            {
                printf("%s is read only, can not rename\n",oldName);
                return -1;
            }

            while(openTrav != NULL)
            {
                if(strcmp(oldName, openTrav->filename) == 0)
                {
                    strcpy(openTrav->filename, newName);
                    break;
                }
                else
                {
                    openTrav = openTrav->next;
                }
            }

            strcpy(trav->filename, newName);
            readBlock(disk, trav->block, buf);
            strncpy(&(buf[4]), newName, FILE_NAME_SIZE);
            if( writeBlock(disk, trav->block, buf) == -1)
            {
                printf("write block failed\n");
                return -1;
            }
            printf("renamed: %s is now %s\n", oldName, newName);
            return 0;
        }
        else
        {
            trav = trav->next;
        }
    }
    printf("rename failed: %s not found\n", oldName);
    return -1;
}


/**
 * Takes disk file pointer as argument, returns 0 on success -1 on failure.  Prints list of files on disk.
 */

int tfs_dir()
{
    FileObj *trav = filesOnDisk->next;

    if(disk <= 0)
    {
        if((disk = openDisk(defaultDiskName, 0)) == -1)
        {
            printf("failed to open disk: %s\n", defaultDiskName);
            return -1;
        }

    }

    if(trav == NULL)
    {
        populateFileList(disk);
        trav = filesOnDisk->next;
    }

    printf("Directory contains: ");
    while(trav != NULL)
    {
        printf("%s ", trav->filename);
        trav = trav->next;
    }
    printf("\n");
    return 0;
}

int tfs_makeRO(char *name)
{
    if(tfs_writeMode(name,RO)== -1)
    {
        return -1;
    }
    else
    {
        printf("%s is now read only\n", name);
        return 0;
    }
}


int tfs_makeRW(char *name)
{
    if( tfs_writeMode(name, RW) == -1)
    {
        return -1;
    }
    else
    {
        printf("%s is now read write\n", name);
        return 0;
    }

}

int tfs_writeMode(char *name, char mode)
{
   /* char buf[256]; */
    FileObj *trav = filesOnDisk->next;
    FileObj *openTrav = openFiles->next;

    if(disk <= 0)
    {
        if((disk = openDisk(defaultDiskName, 0))==-1)
        {
            printf("failed to open %s\n",defaultDiskName);
            return -1;
        }
    }

    if(trav == NULL)
        populateFileList(disk);

    while(trav != NULL)
    {
        if(strcmp(name, trav->filename) == 0)
        {
            while(openTrav != NULL)
            {
                if(strcmp(name, openTrav->filename) == 0)
                {
                    openTrav->mode = mode;
                    break;
                }
                else
                {
                    openTrav = openTrav->next;
                }
            }

            trav->mode = mode;

          /*  readBlock(disk, trav->block, buf);
            buf[INODE_META+BLOCK_META-1] = mode;
            if(writeBlock(disk, trav->block, buf) == -1)
            {
                printf("write block failed\n");
                return -1;
            }*/
            return 0;
        }
        else
        {
            trav = trav->next;
        }
    }
    printf("successfully set to %s\n", mode ? "RO" : "RW");
    return 0;
}

int tfs_writeByte(fileDescriptor FD, unsigned char data)
{
    FileObj *aFile;
    char curBlock[blockSize];
    int numBlocks;
    int nBytes;
    int i;
    int prevB, currB = FD;

    if((aFile = getFileEntry(openFiles, FD)) == NULL)
    {
        printf("Unable to find file.\n");
        return -1;
    }

    if( aFile->mode == RO)
    {
        printf("Write Byte failed: %s is read only\n", aFile->filename);
        return -1;
    }
    
    if(aFile->fp >= aFile->size)
    {
        printf("Error: writeByte at end of file\n");
        return -1;
    }

    numBlocks = ((aFile->fp + INODE_META) / DATA_SIZE) + 1;
    nBytes = ((aFile->fp + INODE_META) % DATA_SIZE) + BLOCK_META;

    for (i = 0; i < numBlocks; i++)
    {
        if (readBlock(disk, currB, curBlock) == -1)
        {
            printf("ReadBlock error\n");
            return -1;
        }
        prevB = currB;
        currB = curBlock[2];
    }

    curBlock[nBytes] = data;
    if(writeBlock(disk, prevB, curBlock) == -1)
    {
        printf("write block failed\n");
        return -1;
    }
    aFile->fp += 1;
    return 0;
}

/**********************
 * Helper Functions.  *
 *************************************************************/
/**
 * sets the code of the given block (as well as the code of the blocks
 * it is pointing to) to FREE
 */
void freeChain(int block)
{
    char buf[blockSize];
    char freeCode = FREE;
    int curBlock = block;

    readBlock(disk, curBlock, buf);
    curBlock = buf[2];
    while (curBlock != 0 && readBlock(disk, curBlock, buf) != -1)
    {
        buf[0] = freeCode;
        writeBlock(disk, curBlock, buf);
        blockStateArr[curBlock] = '0';
        curBlock = buf[2];
    }
    writeVector();
}

/**
 * returns the FileObj structure referencing the file at the given block.
 * if file is not in the list returns NULL.
 */
FileObj *getFileEntry(FileObj *head, int block)
{
    FileObj *cur = head->next;
    while (cur != NULL)
    {
        if(cur->block == block)
            return cur;
        cur = cur->next;
    }
    return NULL;
}

/**
 * returns the block number of the next free block. 0 if no free blocks are
 * found.
 */
int nextFreeBlk()
{
    int i;
    for(i = 1; i < MAX_BLOCKS; i++)
    {
        if(blockStateArr[i] == '0')
            return i;
    }
    return 0;
}

/**
 * returns the number of free blocks in the disk.
 */
int totalFreeBlks()
{
    int i, count = 0;
    for(i = 1; i < MAX_BLOCKS; i++)
    {
        if(blockStateArr[i] == '0')
            count++;
    }
    return count;
}

/* BlockState (free or ocupide)*/
int populateBlockStateArr(int disk)
{
    int i, pos = 0;
    char *vector, buf[blockSize];

    readBlock(disk, SUPER_LOC, buf);
    vector = &(buf[BLOCK_META]);
    for (i = 0; i < 5; i++)
    {
        unpackBits(vector[i], blockStateArr, &pos);
    }

    blockStateArr[0] = '1';
    return 0;
}

int populateFileList(int disk)
{
    char buf[blockSize];
    char fileName[FILE_NAME_SIZE + 1];
    int i;
    /*mode*/
    unsigned int fileSize;
    FileObj *temp;

    /* filesOnDisk->next =NULL; */
    freeList(filesOnDisk->next);


    for (i = 1; i < MAX_BLOCKS; i++)
    {
        if(readBlock(disk, i, buf)  == -1)
        {
            printf("line:%d. Unable to read block\n", __LINE__);
            return -1;
        }

        if (buf[0] == INODE)
        {
            strncpy(fileName, &(buf[BLOCK_META]), FILE_NAME_SIZE);
            fileSize = mergeInt((unsigned char*)&(buf[BLOCK_META + FILE_NAME_SIZE]));
          /*  mode = buf[BLOCK_META + INODE_META - 1]; */
            temp = newFileObj(fileName, i, fileSize, RW);
            
            temp->next = filesOnDisk->next;
            filesOnDisk->next = temp;
        }
    }
    return 0;
}

/******************************************************************/
/* Pack bits until a byte is filled, return the rest of the bits  */
/******************************************************************/
int writeVector ()
{
    int i, byte;
    char *pos, bitVector[256];
    unsigned bitSet = 1;

    pos = blockStateArr;
    bitVector[0] = SUPERBLOCK;
    bitVector[1] = MAGIC_NUM;
    bitVector[2] = 0;
    bitVector[3] = 0;

    for(byte = BLOCK_META; byte < 9; byte++)
    {
        bitVector[byte] = 0;
        for(i = 0; i < 8 && *pos != '\0'; i++, pos++)
            if(*pos == '1')
                bitVector[byte] |= (bitSet << (7 - i));
    }

    for(; byte < blockSize; byte++)
        bitVector[byte] = 0;

    if (writeBlock(disk, SUPER_LOC, bitVector) == -1)
    {
        printf("Unable to write Vector.\n");
        return -1;
    }

    return 0;
}

/******************************************************************/
/* Unpacks bits in a byte                                         */
/******************************************************************/
void unpackBits (unsigned byte, char bits[], int *pos)
{
    int i;
    unsigned mask = 128;
    for (i = 0; i < 8; i++)
    {
        if(mask & byte)
            bits[*pos] = '1';
        else
            bits[*pos] = '0';
        mask = mask >> 1;
        *pos = *pos + 1;
    }
}

void splitInt(unsigned int num, unsigned char *a)
{
    int i;
    for (i = 0; i < 4; i++)
    {
        a[i] = 0;
        a[i] |= num;
        num = num >> 8;
    }
    a[4] = 0;
}

int mergeInt(unsigned char *arr)
{
    int i;
    unsigned int size = 0;
    for (i = 3; i >=0; i--)
    {
        size = size << 8;
        size |= arr[i];
    }
    return size;
}

/**************************
 * Linked list Functions  *
 *******************************************************************/
FileObj *newFileObj (char *fileName, int blockNum, unsigned int fileSize, int mode)
{
    FileObj *newObj = (FileObj *) malloc(sizeof(FileObj));

    newObj->block = blockNum;
    newObj->size = fileSize;
    newObj->fp =  0;
    newObj->mode = mode;
    if (fileName == NULL)
        newObj->filename[0] = '\0';
    else
        strcpy(newObj->filename, fileName);

    newObj->next = NULL;

    return newObj;
}

void freeList (FileObj *obj)
{
    FileObj *cur, *temp;
    cur = obj;
    while(cur != NULL)
    {
        temp = cur;
        cur = cur->next;
        free(temp);
    }
}

